/* generated from gcc rtl data using -fdump-rtl-expand commandline option and rtl2dot.py python script, see below */
digraph callgraph {
"main" -> "prepare4newdata";
"main" -> "exit";
"main" -> "maingtk";
"maingtk" -> "static_maingtk";
"static_maingtk" -> "gtk_window_get_type";
"static_maingtk" -> "gtk_adjustment_new";
"static_maingtk" -> "splay_tree_has_data";
"static_maingtk" -> "gtk_drawing_area_new";
"static_maingtk" -> "gtk_container_add";
"static_maingtk" -> "gtk_widget_set_events";
"static_maingtk" -> "gtk_window_new";
"static_maingtk" -> "gtk_box_new";
"static_maingtk" -> "printf";
"static_maingtk" -> "update_status_text";
"update_status_text" -> "static_update_status_text";
"static_update_status_text" -> "snprintf";
"static_update_status_text" -> "memset";
"static_update_status_text" -> "gtk_text_buffer_set_text";
"static_maingtk" -> "gtk_text_view_get_buffer";
"static_maingtk" -> "gtk_spin_button_new";
"static_maingtk" -> "gtk_button_new_with_label";
"static_maingtk" -> "gtk_menu_item_get_type";
"static_maingtk" -> "gtk_text_view_get_type";
"static_maingtk" -> "once_init";
"static_maingtk" -> "gtk_window_set_title";
"static_maingtk" -> "gtk_menu_bar_new";
"static_maingtk" -> "gtk_widget_add_events";
"static_maingtk" -> "gtk_main";
"static_maingtk" -> "gtk_window_set_default_size";
"static_maingtk" -> "gtk_toggle_button_get_type";
"static_maingtk" -> "gtk_label_new";
"static_maingtk" -> "calculate";
"static_maingtk" -> "gtk_widget_set_app_paintable";
"static_maingtk" -> "randomgraph";
"static_maingtk" -> "gtk_menu_new";
"static_maingtk" -> "calculate_init";
"static_maingtk" -> "gtk_box_pack_start";
"static_maingtk" -> "gtk_widget_show";
"static_maingtk" -> "gtk_box_get_type";
"static_maingtk" -> "prepare4newdata";
"static_maingtk" -> "gtk_text_view_new";
"static_maingtk" -> "parse";
"static_maingtk" -> "gtk_container_get_type";
"static_maingtk" -> "gtk_adjustment_set_value";
"static_maingtk" -> "gtk_text_buffer_set_text";
"static_maingtk" -> "g_type_check_instance_cast";
"static_maingtk" -> "fflush";
"static_maingtk" -> "gtk_menu_item_set_submenu";
"static_maingtk" -> "on_treeview_window_destroy";
"static_maingtk" -> "gtk_check_button_new_with_label";
"static_maingtk" -> "gtk_init_check";
"static_maingtk" -> "gtk_menu_item_new_with_mnemonic";
"static_maingtk" -> "gtk_toggle_button_set_active";
"static_maingtk" -> "gtk_scale_set_draw_value";
"static_maingtk" -> "gtk_scale_get_type";
"static_maingtk" -> "g_signal_connect_data";
"static_maingtk" -> "gtk_scale_new";
"static_maingtk" -> "gtk_adjustment_get_type";
"static_maingtk" -> "gtk_text_view_set_editable";
"main" -> "myfinalmemcheck";
"main" -> "time";
"main" -> "colorcode_clear";
"main" -> "commandline_options";
"main" -> "setlocale";
"main" -> "console_saveas";
"console_saveas" -> "strrchr";
"console_saveas" -> "fprintf";
"console_saveas" -> "maingtk_saveaspng";
"console_saveas" -> "maingtk_saveassvg";
"console_saveas" -> "strcmp";
"main" -> "srand";
}

@

# rtl2dot
Create C callgraphs from  gcc rtldumps via GraphViz.

Based on [egypt](http://www.gson.org/egypt/egypt.html), rewritten in python with added support for configurable root nodes and omission by regex.
May or may not work with C++ code.

## Usage
Compile your code with `-fdump-rtl-expand` (eg. by running `make CFLAGS=-fdump-rtl-expand`).
This will generate some new files, most commonly with the extension `.expand`.

Run `rtl2dot myproject.beepbop.expand | dot -Tsvg > myproject.svg` to get a graph of the `main` function of your project.

Valid options are:
* `--ignore <regex>`	Regular expression describing function names to be ignored
* `--root <function>`	Select the function to use as root node of the graph
* `--local`		Ignore functions that are not defined within the rtl dump (most likely library functions) 

Any other arguments are treated as input files. If no input files are given, input is expected on stdin.

### Example
`./rtl2dot.py smtpd.expand --root core_loop --ignore "client_send|logprintf|common_*" --local | dot -Tsvg > smtpd.svg`

## License
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar and 
reproduced below.

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
Version 2, December 2004 

Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 

	Everyone is permitted to copy and distribute verbatim or modified 
	copies of this license document, and changing it is allowed as long 
	as the name is changed. 

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 

	0. You just DO WHAT THE FUCK YOU WANT TO.

#!/usr/bin/env python3
#
# Author: cbdev <cb@cbcdn.com>
# Reference: https://github.com/cbdevnet/rtl2dot 
#
#This program is free software. It comes without any warranty, to
#the extent permitted by applicable law. You can redistribute it
#and/or modify it under the terms of the Do What The Fuck You Want
#To Public License, Version 2, as published by Sam Hocevar and 
#reproduced below.
#
#DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
#Version 2, December 2004 
#
#Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 
#
#	Everyone is permitted to copy and distribute verbatim or modified 
#	copies of this license document, and changing it is allowed as long 
#	as the name is changed. 
#
#DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
#TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 
#
#	0. You just DO WHAT THE FUCK YOU WANT TO.
#

import fileinput
import re
import sys

root = "main"
ignore = None
infiles = []
local = False

i = 1
# There probably should be sanity checks here, but lets face it: If you cant pass arguments right, this isnt for you
while i < len(sys.argv):
    if sys.argv[i] == "--ignore":
        ignore = re.compile(sys.argv[i + 1])
        i += 1
    elif sys.argv[i] == "--root":
        root = sys.argv[i + 1]
        i += 1
    elif sys.argv[i] == "--local":
        local = True
    elif sys.argv[i] == "--help" or sys.argv[i] == "-h":
        print("Generate call graphs of C programs from gcc rtldumps")
        print("Options:")
        print("\t--ignore <regex>\t\tFunctions to omit from the resulting graph")
        print("\t--root <function>\t\tWhich function to use as root node (default: main)")
        print("\t--local\t\t\t\tOmit functions not defined in the dump (eg. library calls)")
        sys.exit(0)
    else:
        infiles.append(sys.argv[i])
    i+=1

current = ""
calls = {}

func_old = re.compile("^;; Function (?P<func>\S+)\s*$")
func_new = re.compile("^;; Function (?P<mangle>.*)\s+\((?P<func>\S+)(,.*)?\).*$")
funcall = re.compile("^.*\(call.*\"(?P<target>.*)\".*$")
symref = re.compile("^.*\(symbol_ref.*\"(?P<target>.*)\".*$")

def enter(func):
    global current, calls
    current = func
    if calls.get(current, None) is not None:
        print("Ambiguous function name " + current, file=sys.stderr)
    else:
        calls[current] = {}

def call(func, facility):
    global current, calls
    if calls[current].get(func, None) is not None and calls[current][func] != facility:
        print("Ambiguous calling reference to " + func, file=sys.stderr)
    calls[current][func] = facility

def dump(func):
    global calls
    if calls.get(func, None) is None:
        # edge node
        return
    for ref in calls[func].keys():
        if calls[func][ref] == "call":
            # Invalidate the reference to avoid loops
            calls[func][ref] = None
            if local and calls.get(ref, None) is None:
                # non-local function
                continue
            if ignore is None or re.match(ignore, ref) is None:
                print('"' + func + '" -> "' + ref + '";')
                dump(ref)

# Scan the rtl dump into the dict
for line in fileinput.input(infiles):
    if re.match(func_old, line) is not None:
        # print "OLD", re.match(func_old, line).group("func")
        enter(re.match(func_old, line).group("func"))
    elif re.match(func_new, line) is not None:
        # print "NEW", re.match(func_new, line).group("func"), "Mangled:", re.match(func_new, line).group("mangle")
        enter(re.match(func_new, line).group("func"))
    elif re.match(funcall, line) is not None:
        # print "CALL", re.match(funcall, line).group("target")
        call(re.match(funcall, line).group("target"), "call")
    elif re.match(symref, line) is not None:
        # print "REF", re.match(symref, line).group("target")
        call(re.match(symref, line).group("target"), "ref")

print("digraph callgraph {")
dump(root)
print("}")
